package com.tianji.learning.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.api.dto.leanring.LearningRecordFormDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.CacheConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.DelayedTask;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.entity.LearningLesson;
import com.tianji.learning.domain.entity.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.listener.LearningDelayMsgListener;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.*;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author author
 */
@Slf4j
@Service
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {
    @Autowired
    private ILearningLessonService lessonService;

    @Autowired
    private CourseClient courseClient;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RabbitMqHelper mqHelper;

    @Autowired
    @Lazy
    private LearningDelayMsgListener learningDelayMsgListener;

    private RQueue<LearningRecord> queue;
    @Autowired
    private RedissonClient redissonClient;
    //创建延时队列
    private DelayQueue<DelayedTask<LearningRecord>> delayQueue = new DelayQueue<>();
    private ThreadPoolExecutor taskMsgThreadPool = new ThreadPoolExecutor(1, 1, 120, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
    private ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors() * 2,
            120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r);
                }
            }, new ThreadPoolExecutor.CallerRunsPolicy());

    @PostConstruct
    public void postConstruct() {
        taskMsgThreadPool.submit(() -> {
            while (true) {
                log.info("-------开始接受延时消息：{}");
                /*DelayedTask<LearningRecord> delayedTask = delayQueue.take();
                LearningRecord recordMsg = delayedTask.getData();
                bizThreadPool.submit(() -> {
                    log.info("-------接受延时消息：{}--------开始执行", JSONUtil.toJsonStr(recordMsg));
                    learningDelayMsgListener.onLearningDelayMsg(recordMsg);
                });*/
                LearningRecord task = queue.poll();

                bizThreadPool.submit(() -> {
                    learningDelayMsgListener.onLearningDelayMsg(task);
                });
            }
        });
    }

    @PreDestroy
    public void preDestroy() {
        redissonClient.shutdown();
        taskMsgThreadPool.shutdown();
        bizThreadPool.shutdown();
    }

    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        Long userId = UserContext.getUser();
        LambdaQueryWrapper<LearningLesson> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LearningLesson::getCourseId, courseId);
        queryWrapper.eq(LearningLesson::getUserId, userId);
        queryWrapper.orderByDesc(LearningLesson::getUpdateTime);
        LearningLesson lesson = lessonService.getOne(queryWrapper);

        // select * from xx where lesson_id = #{lessonId}
        List<LearningRecord> learningRecords = lambdaQuery().eq(LearningRecord::getLessonId, lesson.getId())
                .eq(LearningRecord::getUserId, userId).list();
        LearningLessonDTO learningLessonDTO = BeanUtil.copyProperties(lesson, LearningLessonDTO.class);
        learningLessonDTO.setRecords((BeanUtils.copyList(learningRecords, LearningRecordDTO.class)));
        return learningLessonDTO;
    }


    @Override
    public void updateLearningRecord(LearningRecordFormDTO dto) {
        Long userId = UserContext.getUser();
        Integer sectionType = dto.getSectionType();
        LearningLesson learningLessonDB = lessonService.getById(dto.getLessonId());
        Long courseId = learningLessonDB.getCourseId();
        if (learningLessonDB == null) {
            log.error("根据lessonid：{}查询不到lesson信息", dto.getLessonId());
            throw new BizIllegalException("网络异常");
        }
        if (sectionType == SectionType.EXAM.getValue()) {
            //考试
            //新增学习记录，insert
            LearningRecord learningRecord = BeanUtil.copyProperties(dto, LearningRecord.class);
            addLearningRecord(learningLessonDB, dto, true);
            //判断是否全部学完
            checkFinishAndUpdataLesson(dto.getSectionId(), learningLessonDB, courseId);

            learningLessonDB.setLatestSectionId(dto.getSectionId());
            learningLessonDB.setLatestLearnTime(LocalDateTime.now());
            lessonService.updateById(learningLessonDB);
        } else {
            //视频
            //存不存在记录
            LambdaQueryWrapper<LearningRecord> queryWrapper =getQueryWrapper(userId, dto.getLessonId(), dto.getSectionId());


            LearningRecord learningRecordDB = getLearningRecord(dto, userId, queryWrapper);
            /*LearningRecord learningRecordDB = this.lambdaQuery().eq(LearningRecord::getLessonId, dto.getLessonId())
                    .eq(LearningRecord::getSectionId, dto.getSectionId())
                    .eq(LearningRecord::getUserId, userId)
                    .one();*/
            //不存在
            if (ObjectUtil.isEmpty(learningRecordDB)) {
                addLearningRecord(learningLessonDB, dto, false);
            } else {
                //存在 //更新学习记录
                LearningRecord learningRecord = BeanUtil.copyProperties(dto, LearningRecord.class);
                learningRecord.setUserId(userId);
                //判断是否第一次学完  //更新课表
                boolean oldFinish = learningRecordDB.getFinished();
                boolean over = dto.getMoment() * 2 >= dto.getDuration();

                boolean isFirstFinish = !oldFinish && over;
                if (isFirstFinish) {
                    learningRecord.setFinished(true);
                    learningRecord.setFirstFinished(true);
                    learningRecord.setFinishTime(LocalDateTime.now());
                } else {
                    learningRecord.setFinished(false);
                    learningRecord.setFirstFinished(false);
                }
                //通用设置   不是第一次学完
                learningRecord.setUpdateTime(LocalDateTime.now());
                learningRecord.setMoment(dto.getMoment());
                log.info("-------更新学习记录：{}", JSONUtil.toJsonStr(learningRecord));
                //不更新这个库 更新redis  使用延时消息，rabbitmq
//                this.update(learningRecord, queryWrapper);
                DelayedTask<LearningRecord> delayedTask = new DelayedTask<>(learningRecord, 20);
                log.info("---开始更新redis：{}",learningRecord);
                String key = loadingKey(userId, dto.getLessonId(), dto.getSectionId());
                redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(learningRecord));
                log.info("-------发送延时消息：{}", JSONUtil.toJsonStr(delayedTask));
//                delayQueue.offer(delayedTask);
                RedissonClient redissonClient = Redisson.create();
                 queue = redissonClient.getQueue("myQueue");
                RDelayedQueue<LearningRecord> delayedQueue = redissonClient.getDelayedQueue(queue);
                delayedQueue.offer(learningRecord, 20, TimeUnit.SECONDS);
               /*
               //方法一
               mqHelper.sendDelayMessage(MqConstants.Exchange.LEARNING_DELAY_EXCHANGE,
                        MqConstants.Key.LEARNING_DELAY_KEY,
                        learningRecord, Duration.ofMinutes(20));*/

                /*//是第一次学完
                if (isFirstFinish) {
                    checkFinishAndUpdataLesson(dto.getSectionId(), learningLessonDB, courseId);

                }*/
            }
        }
    }
    @Override
    public LambdaQueryWrapper<LearningRecord> getQueryWrapper(Long userId, Long lessonId, Long sectionId) {
        LambdaQueryWrapper<LearningRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId)
                .eq(LearningRecord::getUserId, userId);
        return queryWrapper;
    }

    private LearningRecord getLearningRecord(LearningRecordFormDTO dto, Long userId, LambdaQueryWrapper<LearningRecord> queryWrapper) {
        String key = loadingKey(userId, dto.getLessonId(), dto.getSectionId());
        //先查缓存
        String s = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(s)) {
            log.info("-------从缓存中获取学习记录：{}", s);
            LearningRecord learningRecord = JSONUtil.toBean(s, LearningRecord.class);
            return learningRecord;
        }
        //查不到   查数据库   加缓存
        LearningRecord learningRecord = this.getOne(queryWrapper);
        log.info("-------从数据库中获取学习记录：{}", JSONUtil.toJsonStr(learningRecord));
        if (ObjectUtil.isEmpty(learningRecord)){
            return null;
        }
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(learningRecord));
        log.info("-------将学习记录存入缓存：{}", key);
        return learningRecord;
    }

    @Override
    public String loadingKey(Long userId, Long lessonId, Long sectionId) {
        return CacheConstants.LEARNING_RECORD_CACHE + ":" + userId + ":" + lessonId + ":" + sectionId;
    }


    //判断是否全部学完 更新
    @Override
    public void checkFinishAndUpdataLesson(Long sectionId, LearningLesson learningLessonDB, Long courseId) {
        //判断是否全部学完

        Integer oldNum = learningLessonDB.getLearnedSections();
        CourseFullInfoDTO course = courseClient.getCourseInfoById(courseId, false, false);
        if (course == null) {
            log.error("根据courseid：{}查询不到course信息", learningLessonDB.getCourseId());
            throw new BizIllegalException("网络异常");
        }
        //总学时
        Integer sectionNum = course.getSectionNum();
        if (oldNum + 1 >= sectionNum) {
            learningLessonDB.setStatus(LessonStatus.FINISHED.getValue());
        } else {
            learningLessonDB.setStatus(LessonStatus.LEARNING.getValue());
        }
        //如果学完，更新课表的状态为已经学完  //更新课表已经学习的小节数量，最近学习的小节最近学习的时间
        learningLessonDB.setLearnedSections(oldNum + 1);
        learningLessonDB.setLearnedSections(learningLessonDB.getLearnedSections());
        learningLessonDB.setLatestSectionId(sectionId);
        learningLessonDB.setLatestLearnTime(LocalDateTime.now());
        lessonService.updateById(learningLessonDB);
    }

    //新增学习记录
    private void addLearningRecord(LearningLesson lesson, LearningRecordFormDTO dto, boolean finish) {
        // 新增学习记录，insert
        LearningRecord learningRecord = BeanUtil.copyProperties(dto, LearningRecord.class);
        learningRecord.setCourseId(lesson.getCourseId());
        if (finish) {
            learningRecord.setFinished(true);
            learningRecord.setFinishTime(LocalDateTime.now());
        }
        learningRecord.setCreateTime(LocalDateTime.now());
        learningRecord.setUpdateTime(LocalDateTime.now());
        learningRecord.setUserId(UserContext.getUser());
        learningRecord.setSectionType(dto.getSectionType());
        this.save(learningRecord);
    }
}
